summaryrefslogtreecommitdiff
path: root/app/[lng]
diff options
context:
space:
mode:
Diffstat (limited to 'app/[lng]')
-rw-r--r--app/[lng]/admin/edp-progress/page.tsx431
1 files changed, 431 insertions, 0 deletions
diff --git a/app/[lng]/admin/edp-progress/page.tsx b/app/[lng]/admin/edp-progress/page.tsx
new file mode 100644
index 00000000..4efb739c
--- /dev/null
+++ b/app/[lng]/admin/edp-progress/page.tsx
@@ -0,0 +1,431 @@
+"use client";
+
+import React from 'react';
+import { Button } from '@/components/ui/button';
+import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
+import { Input } from '@/components/ui/input';
+import { Label } from '@/components/ui/label';
+import { Badge } from '@/components/ui/badge';
+import { Separator } from '@/components/ui/separator';
+import { ScrollArea } from '@/components/ui/scroll-area';
+import {
+ calculateVendorFormCompletion,
+ getProjectVendorCompletionSummary,
+ calculateVendorContractCompletion,
+ getVendorAllContractsCompletionSummary,
+ getAllVendorsContractsCompletionSummary,
+ getAllProjectsVendorCompletionSummary,
+ type VendorFormCompletionStats,
+ type ProjectVendorCompletionSummary,
+ type VendorAllContractsCompletionSummary
+} from '@/lib/forms/vendor-completion-stats';
+import { Loader, TestTube, BarChart, FileText, TrendingUp } from 'lucide-react';
+import { toast } from 'sonner';
+
+interface TestResult {
+ type: string;
+ data: VendorFormCompletionStats | ProjectVendorCompletionSummary | VendorAllContractsCompletionSummary | unknown;
+}
+
+export default function EDPProgressTestPage() {
+ const [loading, setLoading] = React.useState<string | null>(null);
+ const [results, setResults] = React.useState<TestResult | null>(null);
+
+ // Form inputs
+ const [contractItemId, setContractItemId] = React.useState('123');
+ const [formCode, setFormCode] = React.useState('SPR_LST');
+ const [projectId, setProjectId] = React.useState('1');
+ const [vendorId, setVendorId] = React.useState('1');
+
+ const handleTest = async (testType: string, testFunction: () => Promise<unknown>) => {
+ setLoading(testType);
+ setResults(null);
+
+ try {
+ const result = await testFunction();
+ setResults({ type: testType, data: result });
+
+ if (result) {
+ toast.success(`${testType} 테스트 완료`);
+ } else {
+ toast.warning(`${testType} 결과가 없습니다`);
+ }
+ } catch (error) {
+ console.error(`Error in ${testType}:`, error);
+ toast.error(`${testType} 테스트 실패: ${error instanceof Error ? error.message : '알 수 없는 오류'}`);
+ } finally {
+ setLoading(null);
+ }
+ };
+
+ const renderVendorFormStats = (stats: VendorFormCompletionStats) => (
+ <div className="space-y-4">
+ <div className="grid grid-cols-2 md:grid-cols-4 gap-4">
+ <Card>
+ <CardContent className="p-4">
+ <div className="text-2xl font-bold text-green-600">{stats.completionPercentage}%</div>
+ <p className="text-sm text-muted-foreground">완성도</p>
+ </CardContent>
+ </Card>
+ <Card>
+ <CardContent className="p-4">
+ <div className="text-2xl font-bold">{stats.totalFilledFields}</div>
+ <p className="text-sm text-muted-foreground">입력된 필드</p>
+ </CardContent>
+ </Card>
+ <Card>
+ <CardContent className="p-4">
+ <div className="text-2xl font-bold">{stats.totalRequiredFields}</div>
+ <p className="text-sm text-muted-foreground">총 필드</p>
+ </CardContent>
+ </Card>
+ <Card>
+ <CardContent className="p-4">
+ <div className="text-2xl font-bold">{stats.tagCount}</div>
+ <p className="text-sm text-muted-foreground">태그 수</p>
+ </CardContent>
+ </Card>
+ </div>
+
+ <Card>
+ <CardHeader>
+ <CardTitle className="text-lg">태그별 세부 현황</CardTitle>
+ </CardHeader>
+ <CardContent>
+ <ScrollArea className="h-48">
+ <div className="space-y-2">
+ {stats.detailsByTag.map((tag, index) => (
+ <div key={index} className="flex items-center justify-between p-2 border rounded">
+ <span className="font-medium">{tag.tagNo}</span>
+ <div className="flex items-center gap-2">
+ <Badge variant={tag.completionPercentage >= 80 ? "default" : tag.completionPercentage >= 50 ? "secondary" : "destructive"}>
+ {tag.completionPercentage}%
+ </Badge>
+ <span className="text-sm text-muted-foreground">
+ {tag.filledFields}/{tag.requiredFields}
+ </span>
+ </div>
+ </div>
+ ))}
+ </div>
+ </ScrollArea>
+ </CardContent>
+ </Card>
+ </div>
+ );
+
+ const renderProjectSummary = (summary: ProjectVendorCompletionSummary) => (
+ <div className="space-y-4">
+ <div className="grid grid-cols-2 md:grid-cols-3 gap-4">
+ <Card>
+ <CardContent className="p-4">
+ <div className="text-2xl font-bold text-blue-600">{summary.averageCompletionPercentage}%</div>
+ <p className="text-sm text-muted-foreground">평균 완성도</p>
+ </CardContent>
+ </Card>
+ <Card>
+ <CardContent className="p-4">
+ <div className="text-2xl font-bold">{summary.totalVendors}</div>
+ <p className="text-sm text-muted-foreground">참여 벤더</p>
+ </CardContent>
+ </Card>
+ <Card>
+ <CardContent className="p-4">
+ <div className="text-lg font-bold">{summary.projectCode}</div>
+ <p className="text-sm text-muted-foreground">프로젝트 코드</p>
+ </CardContent>
+ </Card>
+ </div>
+
+ <Card>
+ <CardHeader>
+ <CardTitle className="text-lg">벤더별 완성도</CardTitle>
+ </CardHeader>
+ <CardContent>
+ <ScrollArea className="h-48">
+ <div className="space-y-2">
+ {summary.vendors.map((vendor, index) => (
+ <div key={index} className="flex items-center justify-between p-2 border rounded">
+ <span className="font-medium">{vendor.vendorName}</span>
+ <Badge variant={vendor.completionPercentage >= 80 ? "default" : vendor.completionPercentage >= 50 ? "secondary" : "destructive"}>
+ {vendor.completionPercentage}%
+ </Badge>
+ </div>
+ ))}
+ </div>
+ </ScrollArea>
+ </CardContent>
+ </Card>
+ </div>
+ );
+
+ const renderVendorAllContracts = (summary: VendorAllContractsCompletionSummary) => (
+ <div className="space-y-4">
+ <div className="grid grid-cols-2 md:grid-cols-4 gap-4">
+ <Card>
+ <CardContent className="p-4">
+ <div className="text-2xl font-bold text-purple-600">{summary.overallCompletionPercentage}%</div>
+ <p className="text-sm text-muted-foreground">전체 완성도</p>
+ </CardContent>
+ </Card>
+ <Card>
+ <CardContent className="p-4">
+ <div className="text-2xl font-bold">{summary.totalContracts}</div>
+ <p className="text-sm text-muted-foreground">총 계약</p>
+ </CardContent>
+ </Card>
+ <Card>
+ <CardContent className="p-4">
+ <div className="text-2xl font-bold">{summary.totalForms}</div>
+ <p className="text-sm text-muted-foreground">총 폼</p>
+ </CardContent>
+ </Card>
+ <Card>
+ <CardContent className="p-4">
+ <div className="text-2xl font-bold">{summary.totalFilledFields}/{summary.totalRequiredFields}</div>
+ <p className="text-sm text-muted-foreground">입력 필드</p>
+ </CardContent>
+ </Card>
+ </div>
+
+ <div className="grid grid-cols-1 md:grid-cols-2 gap-4">
+ <Card>
+ <CardHeader>
+ <CardTitle className="text-lg">프로젝트별 분석</CardTitle>
+ </CardHeader>
+ <CardContent>
+ <ScrollArea className="h-48">
+ <div className="space-y-2">
+ {summary.projectBreakdown.map((project, index) => (
+ <div key={index} className="flex items-center justify-between p-2 border rounded">
+ <div>
+ <div className="font-medium">{project.projectName}</div>
+ <div className="text-sm text-muted-foreground">
+ 계약 {project.contractsCount}개, 폼 {project.formsCount}개
+ </div>
+ </div>
+ <Badge variant={project.completionPercentage >= 80 ? "default" : "secondary"}>
+ {project.completionPercentage}%
+ </Badge>
+ </div>
+ ))}
+ </div>
+ </ScrollArea>
+ </CardContent>
+ </Card>
+
+ <Card>
+ <CardHeader>
+ <CardTitle className="text-lg">계약별 세부 현황</CardTitle>
+ </CardHeader>
+ <CardContent>
+ <ScrollArea className="h-48">
+ <div className="space-y-2">
+ {summary.contracts.map((contract, index) => (
+ <div key={index} className="flex items-center justify-between p-2 border rounded">
+ <div>
+ <div className="font-medium">{contract.itemName}</div>
+ <div className="text-sm text-muted-foreground">
+ {contract.projectName} - 폼 {contract.totalForms}개
+ </div>
+ </div>
+ <Badge variant={contract.averageCompletionPercentage >= 80 ? "default" : "secondary"}>
+ {contract.averageCompletionPercentage}%
+ </Badge>
+ </div>
+ ))}
+ </div>
+ </ScrollArea>
+ </CardContent>
+ </Card>
+ </div>
+ </div>
+ );
+
+ return (
+ <div className="container mx-auto p-6 space-y-6">
+ <div className="flex items-center gap-2 mb-6">
+ <TestTube className="h-6 w-6" />
+ <h1 className="text-3xl font-bold">EDP Progress 서버 액션 테스트</h1>
+ </div>
+
+ {/* Input Parameters */}
+ <Card>
+ <CardHeader>
+ <CardTitle className="flex items-center gap-2">
+ <FileText className="h-5 w-5" />
+ 테스트 파라미터
+ </CardTitle>
+ <CardDescription>
+ 아래 값들을 수정하여 다양한 시나리오를 테스트할 수 있습니다.
+ </CardDescription>
+ </CardHeader>
+ <CardContent>
+ <div className="grid grid-cols-2 md:grid-cols-4 gap-4">
+ <div className="space-y-2">
+ <Label htmlFor="contractItemId">Contract Item ID</Label>
+ <Input
+ id="contractItemId"
+ value={contractItemId}
+ onChange={(e) => setContractItemId(e.target.value)}
+ placeholder="123"
+ />
+ </div>
+ <div className="space-y-2">
+ <Label htmlFor="formCode">Form Code</Label>
+ <Input
+ id="formCode"
+ value={formCode}
+ onChange={(e) => setFormCode(e.target.value)}
+ placeholder="SPR_LST"
+ />
+ </div>
+ <div className="space-y-2">
+ <Label htmlFor="projectId">Project ID</Label>
+ <Input
+ id="projectId"
+ value={projectId}
+ onChange={(e) => setProjectId(e.target.value)}
+ placeholder="1"
+ />
+ </div>
+ <div className="space-y-2">
+ <Label htmlFor="vendorId">Vendor ID</Label>
+ <Input
+ id="vendorId"
+ value={vendorId}
+ onChange={(e) => setVendorId(e.target.value)}
+ placeholder="1"
+ />
+ </div>
+ </div>
+ </CardContent>
+ </Card>
+
+ {/* Test Buttons */}
+ <Card>
+ <CardHeader>
+ <CardTitle className="flex items-center gap-2">
+ <BarChart className="h-5 w-5" />
+ 테스트 액션들
+ </CardTitle>
+ </CardHeader>
+ <CardContent>
+ <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
+ <Button
+ onClick={() => handleTest('vendor-form', () =>
+ calculateVendorFormCompletion(Number(contractItemId), formCode)
+ )}
+ disabled={loading !== null}
+ className="h-auto p-4 flex flex-col items-start space-y-2"
+ >
+ {loading === 'vendor-form' && <Loader className="h-4 w-4 animate-spin" />}
+ <div className="font-semibold">벤더 폼 완성도</div>
+ <div className="text-sm opacity-80">특정 벤더의 특정 폼 완성도</div>
+ </Button>
+
+ <Button
+ onClick={() => handleTest('project-summary', () =>
+ getProjectVendorCompletionSummary(Number(projectId), formCode)
+ )}
+ disabled={loading !== null}
+ variant="outline"
+ className="h-auto p-4 flex flex-col items-start space-y-2"
+ >
+ {loading === 'project-summary' && <Loader className="h-4 w-4 animate-spin" />}
+ <div className="font-semibold">프로젝트 요약</div>
+ <div className="text-sm opacity-80">프로젝트의 모든 벤더 완성도</div>
+ </Button>
+
+ <Button
+ onClick={() => handleTest('vendor-contract', () =>
+ calculateVendorContractCompletion(Number(vendorId), Number(contractItemId))
+ )}
+ disabled={loading !== null}
+ variant="outline"
+ className="h-auto p-4 flex flex-col items-start space-y-2"
+ >
+ {loading === 'vendor-contract' && <Loader className="h-4 w-4 animate-spin" />}
+ <div className="font-semibold">벤더 계약 완성도</div>
+ <div className="text-sm opacity-80">특정 벤더의 특정 계약 완성도</div>
+ </Button>
+
+ <Button
+ onClick={() => handleTest('vendor-all-contracts', () =>
+ getVendorAllContractsCompletionSummary(Number(vendorId))
+ )}
+ disabled={loading !== null}
+ variant="secondary"
+ className="h-auto p-4 flex flex-col items-start space-y-2"
+ >
+ {loading === 'vendor-all-contracts' && <Loader className="h-4 w-4 animate-spin" />}
+ <div className="font-semibold">벤더 전체 계약</div>
+ <div className="text-sm opacity-80">벤더의 모든 계약 완성도</div>
+ </Button>
+
+ <Button
+ onClick={() => handleTest('all-vendors', () =>
+ getAllVendorsContractsCompletionSummary()
+ )}
+ disabled={loading !== null}
+ variant="secondary"
+ className="h-auto p-4 flex flex-col items-start space-y-2"
+ >
+ {loading === 'all-vendors' && <Loader className="h-4 w-4 animate-spin" />}
+ <div className="font-semibold">전체 벤더 요약</div>
+ <div className="text-sm opacity-80">모든 벤더의 계약 완성도</div>
+ </Button>
+
+ <Button
+ onClick={() => handleTest('all-projects', () =>
+ getAllProjectsVendorCompletionSummary()
+ )}
+ disabled={loading !== null}
+ variant="secondary"
+ className="h-auto p-4 flex flex-col items-start space-y-2"
+ >
+ {loading === 'all-projects' && <Loader className="h-4 w-4 animate-spin" />}
+ <div className="font-semibold">전체 프로젝트 요약</div>
+ <div className="text-sm opacity-80">모든 프로젝트의 벤더 완성도</div>
+ </Button>
+ </div>
+ </CardContent>
+ </Card>
+
+ <Separator />
+
+ {/* Results */}
+ {results && (
+ <Card>
+ <CardHeader>
+ <CardTitle className="flex items-center gap-2">
+ <TrendingUp className="h-5 w-5" />
+ 테스트 결과: {results.type}
+ </CardTitle>
+ </CardHeader>
+ <CardContent>
+ {!results.data ? (
+ <div className="text-center py-8 text-muted-foreground">
+ 데이터가 없습니다. 파라미터를 확인해주세요.
+ </div>
+ ) : results.type === 'vendor-form' ? (
+ renderVendorFormStats(results.data as VendorFormCompletionStats)
+ ) : results.type === 'project-summary' ? (
+ renderProjectSummary(results.data as ProjectVendorCompletionSummary)
+ ) : results.type === 'vendor-all-contracts' ? (
+ renderVendorAllContracts(results.data as VendorAllContractsCompletionSummary)
+ ) : (
+ <div className="space-y-4">
+ <div className="bg-muted p-4 rounded-lg">
+ <pre className="text-sm overflow-auto max-h-96">
+ {JSON.stringify(results.data, null, 2)}
+ </pre>
+ </div>
+ </div>
+ )}
+ </CardContent>
+ </Card>
+ )}
+ </div>
+ );
+}